package com.smartboot.plus.config;

import com.smartboot.plus.ignite.IgniteCacheRegion;
import com.smartboot.plus.launch.LauncherService;
import com.smartboot.plus.utils.Func;
import com.smartboot.plus.utils.INetUtil;
import org.apache.ignite.Ignite;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CacheRebalanceMode;
import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.cache.PartitionLossPolicy;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.cluster.ClusterState;
import org.apache.ignite.configuration.*;
import org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
import org.apache.ignite.spi.metric.jmx.JmxMetricExporterSpi;
import tech.smartboot.feat.cloud.annotation.Autowired;
import tech.smartboot.feat.cloud.annotation.Bean;
import tech.smartboot.feat.core.common.logging.Logger;
import tech.smartboot.feat.core.common.logging.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * [Ignite工作目录]/
 * ├── binary_meta/                  # 二进制元数据存储
 * │   ├── -963686192.bin            # 类的二进制元数据文件
 * │   ├── -1942027694.bin           # 文件名基于类哈希值
 * │   ├── 517885248.bin
 * │   └── 1851183800.bin
 * │
 * ├── marshaller/                   # 序列化映射存储
 * │   ├── -963686192.classname0     # 类名到二进制ID的映射
 * │   ├── -1942027694.classname0    # 与binary_meta文件对应
 * │   ├── 517885248.classname0
 * │   └── 1851183800.classname0
 * │
 * ├── diagnostic/                   # 诊断数据目录
 * │   ├── snapshots/                # 手动创建的快照
 * │   ├── storage/                  # 诊断数据存储
 * │   ├── lock                      # 目录锁文件
 * │   ├── node00-1445405c-df9d-468a-8144-c6123f669908/   # 节点特定诊断数据
 * │   ├── cache-ignite-sys-cache/   # 系统缓存目录
 * │   │   ├── index.bin             # 缓存索引
 * │   │   └── ...                  # 其他缓存文件
 * │   ├── cache-MqttAclCache/       # MQTT ACL缓存
 * │   │   ├── cache_data.dat        # 缓存数据文件
 * │   │   └── index.bin             # 缓存索引
 * │   └── ...                      # 其他缓存目录
 * │
 * ├── wal/                          # 预写日志目录
 * │   └── node00-1445405c-df9d-468a-8144-c6123f669908/   # 节点特定WAL
 * │       ├── [wal-segment-files]  # WAL段文件 (如: wal-00001.wal)
 * │       └── archive/             # 归档的WAL段 (如果配置)
 * │
 * ├── cache-MqttAuthCache/          # MQTT认证缓存
 * │   ├── cache_data.dat            # 缓存数据
 * │   └── index.bin                 # 缓存索引
 * │
 * ├── cache-MqttClientCache/        # MQTT客户端缓存
 * │   ├── cache_data.dat
 * │   └── index.bin
 * │
 * ├── cache-MqttMetricCache/        # MQTT指标缓存
 * │   ├── cache_data.dat
 * │   └── index.bin
 * │
 * ├── cache-MqttNodeCache/          # MQTT节点缓存
 * │   ├── cache_data.dat
 * │   └── index.bin
 * │
 * ├── cp/                           # 检查点目录
 * │   ├── [checkpoint-files]        # 检查点数据文件
 * │   └── ...
 * │
 * ├── lock                          # 全局锁文件
 * │
 * ├── maintenance_tasks.mntc        # 维护任务状态文件
 * │
 * ├── metastorage/                  # 元数据存储
 * │   ├── [meta-files]              # 集群元数据文件
 * │   └── ...
 * │
 * ├── snp/                          # 快照目录 (自动快照)
 * │   ├── [snapshot-files]
 * │   └── ...
 * │
 * └── TxLog/                        # 事务日志目录
 * ├── [transaction-log-files]   # 事务日志文件
 * └── ...
 */
@Bean
public class IgniteDBConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(IgniteDBConfig.class);

    @Autowired
    private LauncherService launcherService;


    @Bean
    public Ignite igniteInstance() {
        IgniteConfiguration igniteConfiguration = getIgniteConfiguration();
        Ignite ignite = Ignition.start(igniteConfiguration);

        // 集群状态处理
        handleClusterState(ignite);

        // 设置基线拓扑（应在所有节点启动后执行）
        ignite.cluster().setBaselineTopology(ignite.cluster().forServers().nodes());

        return ignite;
    }

    private void handleClusterState(Ignite ignite) {
        ClusterState state = ignite.cluster().state();
        if (state == ClusterState.ACTIVE) return;

        if (state == ClusterState.INACTIVE) {
            ignite.cluster().state(ClusterState.ACTIVE);
            LOGGER.info("集群已激活");
            return;
        }

        // 处理ACTIVE_READ_ONLY状态（分区丢失）
        if (state == ClusterState.ACTIVE_READ_ONLY) {
            handlePartitionLoss(ignite);
        }
    }

    private void handlePartitionLoss(Ignite ignite) {
        // 1. 优先尝试自动恢复
        if (tryRecoverPartitions(ignite)) {
            ignite.cluster().state(ClusterState.ACTIVE);
            return;
        }

        LOGGER.warn("执行强制分区重置 ");
        resetLostPartitionsWithConfirmation(ignite);
        ignite.cluster().state(ClusterState.ACTIVE);

    }


    private boolean tryRecoverPartitions(Ignite ignite) {
        boolean allRecovered = true;
        for (IgniteCacheRegion region : IgniteCacheRegion.values()) {
            if (!checkPartitionRecovery(ignite, region.getCacheName())) {
                allRecovered = false;
            }
        }
        return allRecovered;
    }

    private boolean checkPartitionRecovery(Ignite ignite, String cacheName) {
        Collection<ClusterNode> nodes = ignite.cluster().forCacheNodes(cacheName).nodes();
        if (nodes.isEmpty()) return true; // 无数据节点

        // 检查分区状态
        int lostPartitions = ignite.cache(cacheName).lostPartitions().size();
        if (lostPartitions > 0) {
            LOGGER.warn("缓存[{}]有{}个丢失分区", cacheName, lostPartitions);
            return false;
        }
        return true;
    }

    private void resetLostPartitionsWithConfirmation(Ignite ignite)   {
        // 生产环境增加二次确认
        LOGGER.error("==================================================");
        LOGGER.error("⚠️ 警告：即将清除不可恢复的数据分区");
        LOGGER.error("⚠️ 此操作将在 10 秒后执行，终止进程可取消操作");
        LOGGER.error("==================================================");
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            LOGGER.info("操作已被用户取消");
            return;
        }

        List<String> caches = Arrays.stream(IgniteCacheRegion.values())
                .map(IgniteCacheRegion::getCacheName)
                .collect(Collectors.toList());
        ignite.resetLostPartitions(caches);
    }


//    private void handleClusterState(Ignite ignite) {
//        ClusterState state = ignite.cluster().state();
//        if (state != ClusterState.ACTIVE) {
//            LOGGER.warn("集群处于非活跃状态: {}", state);
//
//            try {
//                if (state == ClusterState.INACTIVE) {
//                    ignite.cluster().state(ClusterState.ACTIVE);
//                    LOGGER.info("集群已激活");
//                } else {
////                    IgniteCacheRegion[] values = IgniteCacheRegion.values();
////                    for (IgniteCacheRegion cacheRegion : values) {
////                        if ( ignite.cluster().forCacheNodes(cacheRegion.getCacheName()).nodes().isEmpty()) {
////                            // 存在数据节点才恢复
////                            ignite.resetLostPartitions(Collections.singleton(cacheRegion.getCacheName()));
////                        }
////                    }
//
//
//                    // 从快照恢复更安全
//                  //  ignite.snapshot().restoreSnapshot("backup_202305", null).get();
//                    // 尝试分区恢复
//                    List<String> caches = Arrays.stream(IgniteCacheRegion.values())
//                            .map(IgniteCacheRegion::getCacheName)
//                            .collect(Collectors.toList());
//
//                    ignite.resetLostPartitions(caches);
//
//                    ignite.cluster().state(ClusterState.ACTIVE);
//                    LOGGER.warn("已重置丢失分区并激活集群");
//                }
//            } catch (Exception e) {
//                LOGGER.error("集群激活失败，请手动干预: {}", e.getMessage());
//                // 此处可添加通知逻辑
//            }
//        }
//    }


    private IgniteConfiguration getIgniteConfiguration() {
        String hostIp = System.getProperty(LauncherService.CONFIG_OPENAPI_HOST);
        int discoveryPort = Integer.parseInt(System.getProperty(LauncherService.CONFIG_CLUSTER_DISCOVERY_SPI_LOCAL_PORT));
        int commPort = Integer.parseInt(System.getProperty(LauncherService.CONFIG_CLUSTER_COMMUNICATION_SPI_LOCAL_PORT));
        String configAddresses = System.getProperty(LauncherService.CONFIG_CLUSTER_ADDRESSES);
        String workDirectory = System.getProperty(LauncherService.CONFIG_CLUSTER_WORK_DIRECTORY);

        // 创建工作目录
        String userHome = System.getProperty("user.home");
        File workDir = new File(userHome, workDirectory);
        if (!workDir.exists() && !workDir.mkdirs()) {
            throw new RuntimeException("无法创建工作目录: " + workDir.getAbsolutePath());
        }

        // 处理集群地址
        List<String> addressList = Arrays.stream(configAddresses.split(","))
                .map(String::trim)
                .filter(s -> !s.isEmpty())
                .distinct()
                .collect(Collectors.toList());

        if (!hostIp.equals("0.0.0.0") && !addressList.contains(hostIp)) {
            addressList.add(hostIp);
            LOGGER.info("添加本地地址到集群: {}", hostIp);
        }

        // 获取主机IP
        if (hostIp.equals("0.0.0.0") || Func.isEmpty(hostIp)) {
            hostIp = INetUtil.getHostIp();
        }

        // 创建Ignite配置
        // 创建 Ignite 主配置对象
        IgniteConfiguration igniteCfg = new IgniteConfiguration();


        // 设置节点绑定的本地IP地址
        // 重要：确保此IP能被集群其他节点访问
        igniteCfg.setLocalHost(hostIp);

        // 配置连接器（REST/ODBC/JDBC访问端点）
        // 设置外部访问的主机地址（通常与本地IP相同）
        igniteCfg.setConnectorConfiguration(new ConnectorConfiguration().setHost(hostIp));

        // 设置Ignite工作目录
        // 此目录将存储临时文件、配置备份等非持久化数据
        igniteCfg.setWorkDirectory(workDir.getAbsolutePath());

        // 设置节点模式：服务端节点（非客户端）
        // 客户端节点不存储数据，仅作为接入点
        igniteCfg.setClientMode(false);

        // 配置所有缓存定义
        // 从枚举类获取缓存配置并应用到节点
        igniteCfg.setCacheConfiguration(getCacheConfigurations(IgniteCacheRegion.values()));

        // 配置数据存储设置（核心持久化配置）
        // 包含WAL、检查点、数据区域等关键设置
        igniteCfg.setDataStorageConfiguration(createStorageConfig(workDir));

        // 配置节点发现机制（集群组建）
        // 使用TCP协议和预定义IP列表发现集群节点
        igniteCfg.setDiscoverySpi(createDiscoverySpi(discoveryPort, addressList));

        // 配置节点间通信机制（数据传输）
        // 处理实际的数据交换和消息传递
        igniteCfg.setCommunicationSpi(createCommunicationSpi(commPort, hostIp));

        // 监控配置
        igniteCfg.setMetricsUpdateFrequency(5_000); // 5秒更新一次
        igniteCfg.setClientFailureDetectionTimeout(30_000); // 30秒（必须大于metricsUpdateFrequency）
        igniteCfg.setMetricExporterSpi(new JmxMetricExporterSpi());
        // 确保满足：clientFailureDetectionTimeout >= metricsUpdateFrequency
        assert igniteCfg.getClientFailureDetectionTimeout() >= igniteCfg.getMetricsUpdateFrequency();


        LOGGER.info("Ignite工作目录: {}", workDir.getAbsolutePath());
        return igniteCfg;
    }

    private DataStorageConfiguration createStorageConfig(File workDir) {
        DataStorageConfiguration storageCfg = new DataStorageConfiguration();

        // 存储基础配置
        storageCfg.setStoragePath(workDir.getAbsolutePath() + "/storage");
        // .setPageSize(4096) .setConcurrencyLevel(16);
        // 使用DELETE策略自动清理
        //storageCfg.setCheckpointFrequency(2 * 60 * 1000); // 2分钟
        //storageCfg.setWalHistorySize(50); // 保留50个检查点对应的WAL
        // WAL配置
        storageCfg.setWalMode(WALMode.FSYNC)
                .setWalPath(workDir.getAbsolutePath() + "/wal")
                .setWalArchivePath(workDir.getAbsolutePath() + "/wal_archive")
                .setWalSegmentSize(128 * 1024 * 1024) // 128MB
                .setWalSegments(20)
                .setMaxWalArchiveSize(8L * 1024 * 1024 * 1024); // 8GB


        // 配置数据一致性保障
        storageCfg.setCheckpointFrequency(3 * 60 * 1000) // 3分钟
                .setCheckpointThreads(4)
                .setCheckpointWriteOrder(CheckpointWriteOrder.RANDOM)
                .setWriteThrottlingEnabled(true); // IO限流

        // 系统区域配置
        SystemDataRegionConfiguration sysRegion = new SystemDataRegionConfiguration();


        sysRegion.setMaxSize(256 * 1024 * 1024); // 256MB
        storageCfg.setSystemDataRegionConfiguration(sysRegion);

        // 数据区域配置
        storageCfg.setDataRegionConfigurations(getDataRegionConfigurations(IgniteCacheRegion.values()));

        return storageCfg;
    }

    private TcpDiscoverySpi createDiscoverySpi(int port, List<String> addresses) {
        // 创建 TCP 发现 SPI 实例
        TcpDiscoverySpi spi = new TcpDiscoverySpi();

        // 配置 IP 查找器（设置集群节点地址）
        TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
        ipFinder.setAddresses(addresses);  // 集群节点地址列表
        spi.setIpFinder(ipFinder);        // 应用 IP 查找器

        // 设置本地发现端口
        spi.setLocalPort(port);  // 通常为 47500

        // ===== 网络优化配置 ===== //

        // 设置套接字操作超时（毫秒）
        // 作用：控制节点间网络操作的超时时间
        // 场景：节点间通信（如心跳检测、拓扑交换）
        // 建议值：LAN 环境 5-30秒，WAN/云环境 10-60秒
        spi.setSocketTimeout(10_000);  //10秒

        // 设置单播消息确认超时（毫秒）
        // 作用：等待消息确认的最大时间
        // 场景：发送拓扑更新后等待其他节点确认

        spi.setAckTimeout(10_000);  // 10秒

        // 设置网络操作总超时（毫秒）
        // 作用：定义网络相关操作的最大允许时间
        // 场景：节点加入、状态同步等复杂操作
        // 建议值：高延迟网络应设置较高值
        spi.setNetworkTimeout(10_000);  // 10秒

        // 设置新节点加入集群超时（毫秒）
        // 作用：控制新节点加入集群的最大等待时间
        // 场景：新节点加入或节点重启后重新加入
        // 建议值：大型集群或慢网络需要更高值
        //spi.setJoinTimeout(0);  // 120秒 (2分钟)
        spi.setJoinTimeout(120_000);   // 120秒（新节点加入超时）
        return spi;
    }

    private TcpCommunicationSpi createCommunicationSpi(int port, String host) {
        // 创建TCP通信SPI实例
        TcpCommunicationSpi commSpi = new TcpCommunicationSpi();

        // 设置通信绑定的本地IP地址
        // 重要：必须与节点发现地址一致，且能被集群其他节点访问
        commSpi.setLocalAddress(host);

        // 设置通信端口号
        // 注意：确保此端口在防火墙中开放，避免端口冲突
        commSpi.setLocalPort(port);

        // ===== 通信优化配置 ===== //

        // 设置初始连接超时（毫秒）
        // 首次尝试建立连接时的等待时间
        // 建议值：5-15秒（生产环境负载较重时可适当增加）
        commSpi.setConnectTimeout(10_000);  // 10秒

        // 设置最大连接超时（毫秒）
        // 连接失败后重试时的最大等待时间（指数退避算法上限）
        // 注意：此值应大于基础连接超时
        commSpi.setMaxConnectTimeout(10 * 60 * 1000);  // 600秒

        // 设置连接失败重试次数
        // 节点断开后尝试重新连接的次数
        // 建议值：50-200（根据网络稳定性调整）
        commSpi.setReconnectCount(10);  // 重试10次


        return commSpi;
    }

    private CacheConfiguration[] getCacheConfigurations(IgniteCacheRegion[] regions) {
        CacheConfiguration[] configs = new CacheConfiguration[regions.length];
        for (int i = 0; i < regions.length; i++) {
            IgniteCacheRegion region = regions[i];

            CacheConfiguration cfg = new CacheConfiguration()
                    .setName(region.getCacheName())
                    .setCacheMode(region.getCacheMode())
                    .setDataRegionName(region.getRegionName())
                    .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL)
                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC)
                    .setBackups(region.backups())
                    .setEagerTtl(true)
                    .setPartitionLossPolicy(PartitionLossPolicy.READ_ONLY_SAFE)
                    .setIndexedTypes(region.getIndexedTypes())
                    .setRebalanceMode(CacheRebalanceMode.ASYNC);

            Optional.ofNullable(region.getExpiryPolicyFactory()).ifPresent(cfg::setExpiryPolicyFactory);
            configs[i] = cfg;
        }
        return configs;
    }

    private DataRegionConfiguration[] getDataRegionConfigurations(IgniteCacheRegion[] regions) {
        DataRegionConfiguration[] configs = new DataRegionConfiguration[regions.length];
        for (int i = 0; i < regions.length; i++) {
            IgniteCacheRegion region = regions[i];

            DataRegionConfiguration regionCfg = new DataRegionConfiguration()
                    .setName(region.getRegionName())
                    .setPersistenceEnabled(region.persistence());

            if (region.maxSize() > 0) {
                regionCfg.setMaxSize(region.maxSize());
            }

            // 持久化区域配置
            if (region.persistence()) {
                regionCfg.setPageReplacementMode(PageReplacementMode.SEGMENTED_LRU);
            }
            // 非持久化区域配置
            else {
                regionCfg.setPageEvictionMode(DataPageEvictionMode.RANDOM_LRU);
            }

            configs[i] = regionCfg;
        }
        return configs;
    }


    /**
     * 递归删除指定路径下的所有文件和子目录，包括该目录本身。
     *
     * @param dirPath 要删除的目录路径
     * @throws IOException 如果发生I/O错误
     */
    public void deleteDirectoryRecursively(Path dirPath) throws IOException {
        // Check if the path exists and is a directory
        if (Files.exists(dirPath) && Files.isDirectory(dirPath)) {
            try (Stream<Path> walk = Files.walk(dirPath)) {
                // Sorted in reverse order to ensure directories are deleted after their contents
                walk.sorted((path1, path2) -> -path1.compareTo(path2))
                        .forEach(path -> {
                            try {
                                Files.delete(path);
                            } catch (IOException e) {
                                LOGGER.info("无法删除文件或目录: " + path.toString());
                                e.printStackTrace();
                            }
                        });
            }
            LOGGER.info("文件夹删除成功: " + dirPath.toString());
        } else {
            LOGGER.info("指定的路径不存在: " + dirPath.toString());
        }
    }
}